Ruby on Rails Authentication and Authorization Update 3.2
Created: 9 March 2013 Modified:This is an update to the source code to modify it for Rails 3.2 and Devise 2.2.3. I have also added some helpful bits to the default page and corrected flaws. You should at least already have Ruby, Ruby On Rails, Devise and CanCan installed. It would be preferable that the reader has gone through the five part article first, though not required.
- Ruby on Rails Authentication and Authorization Part 1
- Ruby on Rails Authentication and Authorization Part 2
- Ruby on Rails Authentication and Authorization Part 3
- Ruby on Rails Authentication and Authorization Part 4
- Ruby on Rails Authentication and Authorization Part 5
- myysecurity source
- Ruby on Rails Authentication and Authorization Update 3.2
- mysecurity 3.2 source
The first difficulty that I found was the lack of a dedicated admin user. Our first goal will be to create the admin@nowhere.com user who has permissions to everything.
First lets create the following script which will encrypt our password for our admin user. Having already installed Devise the bcrypt should already be available.
password-encrypter.rb
require 'bcrypt'
pepper = nil
cost = 10
password='password'
puts encrypted_password = ::BCrypt::Password.create("#{password}#{pepper}", :cost => cost).to_s
We should get something close to the following when running the script
Running password-encrypter.rb script
c:filesarticle-update>ruby password-encrypter.rb
$2a$10$BzIoyBkWO7iW2nZsdVn36eFSvvRrds/T5DjvVM.qnv79aL9lZIXve
This gives us the password so that we can use the SQLite Manager in Firefox to create our admin user. As shown below edit the record with an id of 1 and enter admin@nowhere.com for username/email and the encrypted password returned by your script for the password.
Here is the table …

Double click the record with id of 1 and edit it.

Next we need to use the same methods to create the “administrator” role in the role table.

Now we need to associate this role with our user by editing the user_role table and entering a user id of 1 and a role of id of 1 which we just edited and created.

Finally we go into the role_permissions table and add “all” for the controller and “manage” for the permissions.

We now have an admin user whom we have assigned management privileges to all controller actions. Now our journey can truly begin.
If you are like me you find it annoying to have to remember the URL addresses and type them into the application. Lets edit the static landing page for URL http://localhost:3000 so that it has links we can use.
public/index.html
<!DOCTYPE html>
<html>
<head>
<title>Ruby on Rails: MySecurity Example</title>
<style type="text/css" media="screen">
body {
margin: 0;
margin-bottom: 25px;
padding: 0;
background-color: #f0f0f0;
font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana";
font-size: 13px;
color: #333;
}
h1 {
font-size: 28px;
color: #000;
}
a {color: #03c}
a:hover {
background-color: #03c;
color: white;
text-decoration: none;
}
#page {
background-color: #f0f0f0;
width: 750px;
margin: 0;
margin-left: auto;
margin-right: auto;
}
#content {
float: left;
background-color: white;
border: 3px solid #aaa;
border-top: none;
padding: 25px;
width: 500px;
}
#sidebar {
float: right;
width: 175px;
}
#footer {
clear: both;
}
#header, #about, #getting-started {
padding-left: 75px;
padding-right: 30px;
}
#header {
background-image: url("/assets/rails.png");
background-repeat: no-repeat;
background-position: top left;
height: 64px;
}
#header h1, #header h2 {margin: 0}
#header h2 {
color: #888;
font-weight: normal;
font-size: 16px;
}
#about h3 {
margin: 0;
margin-bottom: 10px;
font-size: 14px;
}
#about-content {
background-color: #ffd;
border: 1px solid #fc0;
margin-left: -55px;
margin-right: -10px;
}
#about-content table {
margin-top: 10px;
margin-bottom: 10px;
font-size: 11px;
border-collapse: collapse;
}
#about-content td {
padding: 10px;
padding-top: 3px;
padding-bottom: 3px;
}
#about-content td.name {color: #555}
#about-content td.value {color: #000}
#about-content ul {
padding: 0;
list-style-type: none;
}
#about-content.failure {
background-color: #fcc;
border: 1px solid #f00;
}
#about-content.failure p {
margin: 0;
padding: 10px;
}
#getting-started {
border-top: 1px solid #ccc;
margin-top: 25px;
padding-top: 15px;
}
#getting-started h1 {
margin: 0;
font-size: 20px;
}
#getting-started h2 {
margin: 0;
font-size: 14px;
font-weight: normal;
color: #333;
margin-bottom: 25px;
}
#getting-started ol {
margin-left: 0;
padding-left: 0;
}
#getting-started li {
font-size: 18px;
color: #888;
margin-bottom: 25px;
}
#getting-started li h2 {
margin: 0;
font-weight: normal;
font-size: 18px;
color: #333;
}
#getting-started li p {
color: #555;
font-size: 13px;
}
#sidebar ul {
margin-left: 0;
padding-left: 0;
}
#sidebar ul h3 {
margin-top: 25px;
font-size: 16px;
padding-bottom: 10px;
border-bottom: 1px solid #ccc;
}
#sidebar li {
list-style-type: none;
}
#sidebar ul.links li {
margin-bottom: 5px;
}
.filename {
font-style: italic;
}
</style>
<script type="text/javascript">
function about() {
info = document.getElementById('about-content');
if (window.XMLHttpRequest)
{ xhr = new XMLHttpRequest(); }
else
{ xhr = new ActiveXObject("Microsoft.XMLHTTP"); }
xhr.open("GET","rails/info/properties",false);
xhr.send("");
info.innerHTML = xhr.responseText;
info.style.display = 'block'
}
</script>
<script src="/assets/jquery.js?body=1" type="text/javascript"></script>
<script src="/assets/jquery_ujs.js?body=1" type="text/javascript"></script>
</head>
<body>
<div id="page">
<div id="sidebar">
<ul id="sidebar-items">
.
<h3>Browse the documentation</h3>
<ul class="links">
. [Rails Guides](http://guides.rubyonrails.org/)
. [Rails API](http://api.rubyonrails.org/)
. [Ruby core](http://www.ruby-doc.org/core/)
. [Ruby standard library](http://www.ruby-doc.org/stdlib/)
</div>
<div id="content">
<div id="header">
<h1>Welcome aboard</h1>
<h2>Ruby On Rails: Mysecurity Example</h2>
</div>
<div id="about">
<h3><a href="rails/info/properties" onclick="about(); return false">About your application’s environment]</h3>
<div id="about-content" style="display: none"></div>
</div>
<div id="getting-started">
<h1>Getting started</h1>
<h2>Here’s how to get rolling:</h2>
.
<h2>Read this article</h2>
<p><a href='/2012/02/ruby-on-rails-authentication-and-authorization-part-1/'>Ruby on Rails Authentication and Authorization]</p>
.
<h2>Helpful Links</h2>
<p>Log in - [/user/sign_in](/user/sign_in)</p>
<p>Log out - <a href="/user/sign_out" data-method="delete" rel="nofollow">/user/sign_out]</p>
<p>Roles - [/roles](roles)</p>
<p>Users - [/users](users)</p>
</div>
</div>
<div id="footer"> </div>
</div>
</body>
</html>
Now we have a friendly landing that is a little helpful. Next lets update our gemfile to indicate rails 3.2 and to update our sass versions.
public/index.rb
source 'http://rubygems.org'
gem 'rails', '3.2.11'
gem 'sqlite3'
gem 'devise'
gem 'cancan'
# Gems used only for assets and not required
# in production environments by default.
group :assets do
gem 'sass-rails', '~> 3.2.6'
gem 'uglifier', '>= 1.0.3'
end
gem 'jquery-rails'
group :test do
gem 'turn', '~> 0.8.3', :require => false
end
Now Rails application is configured but if you play around with it you will realize that the Role Permissions fail to load when on the Edit Role screen. This is because rails 3.2 handles helper files differently and it handles layouts differently.
Helper files are to be used with the View of the MVC model not with the controller which is what we chose to do the first time around.
The contents of role_permissions_helper.rb should be copied and pasted to the end of the role_permissions_controller.rb file. The
“layout nil” at the beginning of the controller no longer works to suppress the layout for a controller. Now instead we add
“render :layout => nil if request.xhr?” to the “format.html” lines in the controller.
app/controllers/role_permissions_controller.rb
class RolePermissionsController < ApplicationController
load_and_authorize_resource
# GET /role_permissions
# GET /role_permissions.json
def index
index_helper
respond_to do |format|
format.html { render :layout => nil if request.xhr? } # index.html.erb
format.json { render json: @role_permissions }
end
end
# GET /role_permissions/1
# GET /role_permissions/1.json
def show
@role_permission = RolePermission.find(params[:id])
#authorize! :show, @role_permission
respond_to do |format|
format.html { render :layout => nil if request.xhr? } # show.html.erb
format.json { render json: @role_permission }
end
end
# GET /role_permissions/new
# GET /role_permissions/new.json
def new
prepare
@role_permission = RolePermission.new
@role_permission.role_id = params[:role_id]
respond_to do |format|
format.html { render :layout => nil if request.xhr? }# new.html.erb
format.json { render json: @role_permission }
end
end
# GET /role_permissions/1/edit
def edit
prepare
@role_permission = RolePermission.find(params[:id])
respond_to do |format|
format.html { render :layout => nil if request.xhr? }# new.html.erb
format.json { render json: @role_permission }
end
end
# POST /role_permissions
# POST /role_permissions.json
def create
@role_permission = RolePermission.new(params[:role_permission])
# @role_permission = RolePermission.new()
respond_to do |format|
if @role_permission.save
format.html {
index_helper
render :action => 'index', :layout => nil if request.xhr?
}
format.json { render json: @role_permission, status: :created, location: @role_permission }
else
format.html { render action: "new", :layout => nil if request.xhr? }
format.json { render json: @role_permission.errors, status: :unprocessable_entity }
end
end
end
# PUT /role_permissions/1
# PUT /role_permissions/1.json
def update
@role_permission = RolePermission.find(params[:id])
respond_to do |format|
if @role_permission.update_attributes(params[:role_permission])
format.html {
index_helper
render :action => 'index', :layout => nil if request.xhr?
}
format.json { head :ok }
else
format.html { render action: "edit" }
format.json { render json: @role_permission.errors, status: :unprocessable_entity }
end
end
end
# DELETE /role_permissions/1
# DELETE /role_permissions/1.json
def destroy
@role_permission = RolePermission.find(params[:id])
@role_permission.destroy
respond_to do |format|
format.html {
index_helper
render :action => 'index', :layout => nil if request.xhr?
}
format.json { head :ok }
end
end
#private :prepare
private
def index_helper
if params[:role_id].nil?
if params[:role_permission][:role_id].nil?
log_error " role_id parameter is invalid!"
else
@the_role_id = params[:role_permission][:role_id]
end
else
@the_role_id = params[:role_id]
end
begin
@role_permissions = RolePermission.find(:all,:conditions => ["role_id=?",@the_role_id])
rescue ActiveRecord::RecordNotFound
log_error "record not found role_id=" + params[:role_id]
end
end
def prepare
Rails.application.eager_load!
@roles = Role.all
@regulators = get_models
@conducts = get_actions
end
def get_models
regulators = Array.new
ActiveRecord::Base.descendants.each{ |model|
regulators[regulators.length] = model.name
}
return regulators
end
def get_actions
conducts = Array.new
ApplicationController.descendants.each { |regulator|
the_actions = regulator.action_methods
the_actions.each {|the_action|
conducts[conducts.length] = the_action
}
}
return conducts
end
end
Additionally we need to go to appassetsjavascripts and delete the role_permissions.js.coffee file. With the controller updated and the file deleted we should now have a working role permissions controller.
Now comes the big finale! We modify the application_controller.rb to call Devise authentication. The application_controller is the parent of our other controllers and thus through the magic of inheritance all our controllers will that authentication.
app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
protect_from_forgery
before_filter :authenticate_user!
end
Finally make sure that your users controller and your role controller have the “load_and_authorize_resource” right after the class declaration.
app/controllers/users_controller.rb
class UsersController < ApplicationController
load_and_authorize_resource
#I cut out the rest for the sake of brevity.
end
Your Ruby on Rails 3.2 with authentication and authorization should be good to go! Happy programming.
tags: authentication - authorization - RoR - Ruby - Ruby on Rails